package watch

import (
	"bytes"
	zap "go.uber.org/zap"
	"net/http"
	"os"
	"os/exec"
	"service_keepalive/alarm"
	"service_keepalive/config"
	"service_keepalive/logger"
	"strings"
	"time"
)

var (
	watchInfos []*WatchInfo
	c          chan bool
)

// 监控信息
type WatchInfo struct {
	// 应用名称
	appName string
	// 失败次数
	failTime int
	// 启动脚本
	restartScriptUrl string
	// 监听链接
	watchUrls []string
	// 最大失败次数
	maxFail int
	// 是否可以执行任务
	doTask bool
	// 检查任务的间隔
	internal int64
	// 信道
	c chan bool
}

func Setup() {
	if len(config.CONF_INSTANCE.Watchs) == 0 {
		logger.Fatal("配置信息为空")
		os.Exit(-1)
	}
	logger.Info("输出配置信息:", zap.Any("configInfo", config.CONF_INSTANCE.Watchs))
	watchInfos = make([]*WatchInfo, 0)
	c = make(chan bool, len(config.CONF_INSTANCE.Watchs))
	for _, v := range config.CONF_INSTANCE.Watchs {
		watchInfos = append(watchInfos, &WatchInfo{
			failTime:         0,
			restartScriptUrl: v.RestartScriptUrl,
			watchUrls:        v.WatchUrls,
			maxFail:          v.MaxFail,
			doTask:           true,
			internal:         v.Internal,
			appName:          v.AppName,
			c:                c,
		})
	}
	startWatch()
}

// 开始监控
func startWatch() {
	for _, v := range watchInfos {
		go func(item *WatchInfo) {
			defer func() {
				if r := recover(); r != nil {

					logger.Error("recover in f")
				}
			}()
			for true {
				time.Sleep(time.Second * time.Duration(item.internal))
				watch(item)
			}
		}(v)
	}

	<-c

}

// 监控实例
func watch(item *WatchInfo) {
	if isAlive(item) {
		if !item.doTask {
			toAlive(item)
		}
		item.doTask = true
		item.failTime = 0
	} else {
		if !item.doTask {
			logger.Info("启动中，不监视:", zap.String("appName", item.appName))
			return
		}
		item.failTime++
		if item.failTime > item.maxFail {
			item.doTask = false
			item.failTime = 0
			toDead(item)
		}
	}
}

// 应用恢复
func toAlive(item *WatchInfo) {
	go alarm.ALARM_INSTANCE.Alarm(item.appName, strings.Join(item.watchUrls, ","), "UP")
	logger.Info("启动成功，继续监视:", zap.String("appName", item.appName))
}

// 应用离线
func toDead(item *WatchInfo) {
	go alarm.ALARM_INSTANCE.Alarm(item.appName, strings.Join(item.watchUrls, ","), "OFFLINE")
	restartApp(item)
}

// 判断实例是否存活
func isAlive(item *WatchInfo) bool {
	for _, v := range item.watchUrls {
		testResult := headTest(v)
		if testResult {
			if item.failTime > 0 {
				logger.Info("已恢复:", zap.String("appName", item.appName))
				item.failTime = 0
			}
			return true
		}
	}
	return false
}

func headTest(url string) bool {
	resp, err := http.Head(url)
	if err != nil {
		logger.Error("心跳失败:", zap.String("url", url), zap.Error(err))
		return false
	}
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return true
	} else {
		logger.Error("心跳失败:", zap.String("url", url), zap.Int("statusCode", resp.StatusCode))
		return false
	}

}

// 重启应用
func restartApp(item *WatchInfo) {
	if item.restartScriptUrl == "" {
		logger.Error("脚本内容为空，不执行重启任务:", zap.String("appName", item.appName))
		return
	}
	logger.Info("重新启动应用，执行脚本:", zap.String("appName", item.appName), zap.String("脚本", item.restartScriptUrl))
	execShell(item.restartScriptUrl)

}

// 执行shell脚本
func execShell(s string) {
	cmd := exec.Command("/bin/bash", "-c", s)
	var out bytes.Buffer

	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		logger.Error("执行脚本异常:", zap.String("脚本", s))
	}
	logger.Info("执行脚本成功:", zap.String("返回信息", out.String()))
}
